package com.aispeech.aicube.robot;

import android.app.Application;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;

import com.aispeech.aicube.R;
import com.aispeech.kociemba.KociembaEngine;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MyBrain {
    private final String TAG = "MyBrain";
    private MyVision myVision;
    private MyVoice myVoice;
    private static Application mContext;

    private static final int START_PROCESS_CNT = 10;

    private static final int MSG_FOUND_CUBE = 1;
    private static final int MSG_PLAY_RECOVER_STEP = 2;
    private static final int MSG_RE_3_STEP = 3;

    private Map<String, Map<String, Integer>> mCubeMatrix2;
    private List<String> mCubeRecoverSteps;
    private int mRecStepIndex = 0;
    private static final long PLAY_RECOVER_STEP_INTERVAL = 2800;
    private static final long FIRST_PLAY_RECOVER_STEP_INTERVAL = 4000;

    private KociembaEngine mEngine;
    private HandlerThread mBrPThread;
    private Handler mPhandler;

//    private boolean isVoiceReady = false;

    public enum BRAIN_STATE {
        SLEEPING, IDLE, FINDING, CALCULATING, READY_SHOW, INTERACTING
    }

    public enum BRAIN_EVENT {
        VOICE_EVENT_SHOW, VOICE_EVENT_REVISE, VOICE_EVENT_WAIT, VISION_EVENT_FIND_CUBE
    }

    private BRAIN_STATE mState = BRAIN_STATE.SLEEPING;

    public BRAIN_STATE getState() {
        return mState;
    }

    public final static Map<String, String> STEP_SIGN2PLAY_MAP;
    public final static Map<String, String> CHINESE_COLOR_NAME_MAP;

    static {
        STEP_SIGN2PLAY_MAP = new HashMap<>(18);
        STEP_SIGN2PLAY_MAP.put("F", "前顺顺");
        STEP_SIGN2PLAY_MAP.put("F'", "前逆逆");
        STEP_SIGN2PLAY_MAP.put("F2", "前一圈圈");
        STEP_SIGN2PLAY_MAP.put("B", "后顺顺");
        STEP_SIGN2PLAY_MAP.put("B'", "后逆逆");
        STEP_SIGN2PLAY_MAP.put("B2", "后一圈圈");
        STEP_SIGN2PLAY_MAP.put("L", "左顺顺");
        STEP_SIGN2PLAY_MAP.put("L'", "左逆逆");
        STEP_SIGN2PLAY_MAP.put("L2", "左一圈圈");
        STEP_SIGN2PLAY_MAP.put("R", "右顺顺");
        STEP_SIGN2PLAY_MAP.put("R'", "右逆逆");
        STEP_SIGN2PLAY_MAP.put("R2", "右一圈圈");
        STEP_SIGN2PLAY_MAP.put("U", "上顺顺");
        STEP_SIGN2PLAY_MAP.put("U'", "上逆逆");
        STEP_SIGN2PLAY_MAP.put("U2", "上一圈圈");
        STEP_SIGN2PLAY_MAP.put("D", "下顺顺");
        STEP_SIGN2PLAY_MAP.put("D'", "下逆逆");
        STEP_SIGN2PLAY_MAP.put("D2", "下一圈圈");

        CHINESE_COLOR_NAME_MAP = new HashMap<>(6);
        CHINESE_COLOR_NAME_MAP.put("WHITE", "找到白色");
        CHINESE_COLOR_NAME_MAP.put("RED", "找到红色");
        CHINESE_COLOR_NAME_MAP.put("GREEN", "找到绿色");
        CHINESE_COLOR_NAME_MAP.put("YELLOW", "找到黄色");
        CHINESE_COLOR_NAME_MAP.put("ORANGE", "找到橙色");
        CHINESE_COLOR_NAME_MAP.put("BLUE", "找到蓝色");
    }


    private MyBrain() {
    }


    private static class SingletonHolder {
        private static final MyBrain INSTANCE = new MyBrain();
    }

    private synchronized void changeState(BRAIN_STATE state) {
        Log.d(TAG, "changeState() :mState = [" + mState + "] , set to state = [" + state + "]");
        switch (mState) {
            case SLEEPING:
                if (BRAIN_STATE.IDLE.equals(state)) {
//                    myVoice.speak(mContext.getString(R.string.app_name));
                    mState = BRAIN_STATE.IDLE;
                }
                break;
            case IDLE:
                if (BRAIN_STATE.FINDING.equals(state)) {
//                    myVoice.speak(mContext.getString(R.string.brain_idle2finding));
                    mState = BRAIN_STATE.FINDING;
                }
                break;
            case CALCULATING:
                if (BRAIN_STATE.FINDING.equals(state)) {
                    myVoice.speak(mContext.getString(R.string.brain_calculating2finding));
                    mState = BRAIN_STATE.FINDING;
                } else if (BRAIN_STATE.READY_SHOW.equals(state)) {

                    myVoice.speak(mContext.getString(R.string.brain_calculating2ready_show, mCubeRecoverSteps.size()));
                    mState = BRAIN_STATE.READY_SHOW;
                }
                break;
            case FINDING:
                if (BRAIN_STATE.CALCULATING.equals(state)) {
                    myVoice.speak(mContext.getString(R.string.brain_finding2calculating));
                    mState = BRAIN_STATE.CALCULATING;
                }
            case READY_SHOW:
                if (BRAIN_STATE.INTERACTING.equals(state)) {
                    mState = BRAIN_STATE.INTERACTING;
                }
            case INTERACTING:
                if (BRAIN_STATE.IDLE.equals(state)) {
                    myVoice.speak(mContext.getString(R.string.brain_interacting_finish));
                    mState = BRAIN_STATE.IDLE;
                    mRecStepIndex = 0;
                }
                break;

        }
        //all state can change to sleep
        if (BRAIN_STATE.SLEEPING.equals(state)) {
            mState = BRAIN_STATE.SLEEPING;
        }

//        Log.w(TAG, "changeState: can not update state");
//        return false;
    }

    public static MyBrain getInstance() {
        return SingletonHolder.INSTANCE;
    }

    public void wakeupBrain(Application context) {
        Log.d(TAG, "wakeupBrain: ");
        if (BRAIN_STATE.SLEEPING.equals(mState)){
            mContext = context;
            MyMemory.getInstance().init();

            initProcessHandler();
            changeState(BRAIN_STATE.IDLE);
        }
    }

    private void initProcessHandler() {
        Log.d(TAG, "initProcessHandler: ");

        mBrPThread = new HandlerThread("brPThread");
        mBrPThread.start();
        mPhandler = new Handler(mBrPThread.getLooper()) {
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case MSG_FOUND_CUBE:
                        String res = tryCalculateRecoverStep();
                        if (TextUtils.isEmpty(res)) {
                            changeState(BRAIN_STATE.FINDING);
                        } else {
                            mCubeRecoverSteps.addAll(Arrays.asList(res.split(" ")));
                            changeState(BRAIN_STATE.READY_SHOW);
//                            playRecoverStep1by1();
                            mPhandler.sendEmptyMessageDelayed(MSG_PLAY_RECOVER_STEP, FIRST_PLAY_RECOVER_STEP_INTERVAL);
                        }
                        break;
                    case MSG_PLAY_RECOVER_STEP:

                        if (mPhandler.hasMessages(MSG_PLAY_RECOVER_STEP)) {
                            mPhandler.removeMessages(MSG_PLAY_RECOVER_STEP);
                        }

                        playRecoverStep1by1();
//                        mPhandler.sendEmptyMessageDelayed(MSG_PLAY_RECOVER_STEP, PLAY_RECOVER_STEP_INTERVAL);
                        break;
                    case MSG_RE_3_STEP:
                        mRecStepIndex -= 3;
                        mPhandler.sendEmptyMessageDelayed(MSG_PLAY_RECOVER_STEP, PLAY_RECOVER_STEP_INTERVAL);
                        break;
                }
            }
        };
    }

    private void playRecoverStep1by1() {
        if (mCubeRecoverSteps.size() > 0 && mRecStepIndex < mCubeRecoverSteps.size()) {
            String stepSign = mCubeRecoverSteps.get(mRecStepIndex);
            Log.d(TAG, "playRecoverStep1by1: stepSign[" + mRecStepIndex + "] = " + stepSign);
            if (!TextUtils.isEmpty(stepSign)) {
                changeState(BRAIN_STATE.INTERACTING);

                String stepPrefix = mContext.getString(R.string.brain_interacting_step_play_prefix, mRecStepIndex + 1);
                String playStep = stepPrefix + STEP_SIGN2PLAY_MAP.get(stepSign);
                Log.d(TAG, "handleMessage: pstep = " + playStep);
                myVoice.speak(playStep);
                mRecStepIndex++;
            }
            mPhandler.sendEmptyMessageDelayed(MSG_PLAY_RECOVER_STEP, PLAY_RECOVER_STEP_INTERVAL);
        } else if (mCubeRecoverSteps.size() > 0 && mRecStepIndex == mCubeRecoverSteps.size()) {
            changeState(BRAIN_STATE.IDLE);
        }
    }

    public void sleepBrain() {
        Log.d(TAG, "sleepBrain: ");
        changeState(BRAIN_STATE.SLEEPING);
        //todo release res

        if (null != mPhandler) {
            mPhandler.removeCallbacksAndMessages(null);
            mBrPThread.quitSafely();
        }
    }

    public void openEyes(MyVision eyes) {
        Log.d(TAG, "openEyes: ");

        myVision = eyes;
        myVision.open(new MyVision.EyesListener() {
            @Override
            public void onSee(String things) {
//                Log.d(TAG, "onSee: " + things);
                if (BRAIN_STATE.FINDING.equals(mState)) {
                    processEvent(BRAIN_EVENT.VISION_EVENT_FIND_CUBE, things);
                } else {
                    //blindness
                    Log.d(TAG, "onSee: blindness: " + mState);
                }
            }
        });
//        mCubeMatrix = new HashMap<>();
        mCubeMatrix2 = new HashMap<>();
        mCubeRecoverSteps = new ArrayList<>();
        mEngine = new KociembaEngine();
    }

    public void closeEyes() {
        Log.d(TAG, "closeEyes: ");
        if (null != myVision) {
            myVision.close();
        }
    }

    public void openVoice(MyVoice voice) {
        Log.d(TAG, "openVoice: ");
        myVoice = voice;
        myVoice.open(new MyVoice.VoiceListener() {
            @Override
            public void onVoiceOpen() {
//                isVoiceReady = true;
            }

            @Override
            public void onVoiceClose() {
//                isVoiceReady = false;
            }

            @Override
            public void onVoiceCMD(BRAIN_EVENT event) {
                processEvent(event, "");
            }
        });

    }

    public void closeVoice() {
        Log.d(TAG, "closeVoice: ");
        if (null != myVoice) {
            myVoice.close();
        }
    }

    private void processEvent(BRAIN_EVENT event, String args) {
        Log.d(TAG, "processEvent() called with: event = [" + event + "], args = [" + args + "]");
        switch (event) {
            case VOICE_EVENT_SHOW:
                changeState(BRAIN_STATE.FINDING);
                break;
            case VOICE_EVENT_REVISE:
                //todo
                if (mPhandler.hasMessages(MSG_RE_3_STEP)) {
                    mPhandler.removeMessages(MSG_RE_3_STEP);
                }
                mPhandler.sendEmptyMessage(MSG_RE_3_STEP);
                break;
            case VOICE_EVENT_WAIT:
                //todo

                break;
            case VISION_EVENT_FIND_CUBE:
                String centerColor = mapColor2Coordinate(args);
                Log.d(TAG, "processEvent: centerColor = " + centerColor);
                Log.d(TAG, "processEvent: countAspectColor = " + countAspectColor(centerColor));
                if (countAspectColor(centerColor) == START_PROCESS_CNT) {
                    myVoice.speak(CHINESE_COLOR_NAME_MAP.get(centerColor));
                }
                mPhandler.sendEmptyMessage(MSG_FOUND_CUBE);
                break;
            default:

                break;
        }
    }

    /**
     * in my brain , i like this color coordinate system
     * front - green
     * back  - blue
     * left  - orange
     * right - red
     * up    - white
     * down  - yellow
     *
     * @param colorRst color result
     * @return aspect centre color
     */
    private String mapColor2Coordinate(String colorRst) {
        String[] colors = colorRst.split(":");
//        Log.d(TAG, "mapColor2Coordinate: " + Arrays.toString(colors));
        StringBuilder stringBuilder = new StringBuilder();
        String centreColor = "";
        for (int i = 0; i < colors.length; ++i) {
            switch (colors[i]) {
                case "WHITE":
                    stringBuilder.append("U");
                    break;
                case "YELLOW":
                    stringBuilder.append("D");
                    break;
                case "RED":
                    stringBuilder.append("R");
                    break;
                case "BLUE":
                    stringBuilder.append("B");
                    break;
                case "GREEN":
                    stringBuilder.append("F");
                    break;
                case "ORANGE":
                    stringBuilder.append("L");
                    break;
            }
            if (i == 4) {
                centreColor = colors[i];
            }
        }
        //version 1: add first get color
//        if (!mCubeMatrix.containsKey(centreColor)) {
//            Log.d(TAG, "mapColor2Coordinate: " + centreColor);
//            Log.d(TAG, "mapColor2Coordinate: " + stringBuilder.toString());
//            mCubeMatrix.put(centreColor, stringBuilder.toString());
//            String resultStep = tryCalculateRecoverStep();
//            Log.d(TAG, "mapColor2Coordinate: " + resultStep);
//        } else {
//            //already put
//        }
        //version 2: all color add in map,than record count.
        String aspectColorColloction = stringBuilder.toString();
        if (mCubeMatrix2.containsKey(centreColor)) {
            Map<String, Integer> existMap = mCubeMatrix2.get(centreColor);

            if (existMap.containsKey(aspectColorColloction)) {
                int emergeTimes = existMap.get(aspectColorColloction);
                Log.d(TAG, aspectColorColloction + ": emergeTimes [" + emergeTimes + "]");
                existMap.put(aspectColorColloction, ++emergeTimes);

            } else {
                existMap.put(aspectColorColloction, 1);
            }

        } else {
            //todo maybe not put here play
//            myVoice.speak(centreColor);

            HashMap<String, Integer> aspectColorMap = new HashMap<>();
            aspectColorMap.put(aspectColorColloction, 1);
            mCubeMatrix2.put(centreColor, aspectColorMap);
        }

        return centreColor;
    }

    private int countAspectColor(String color) {
        if (null != mCubeMatrix2 && mCubeMatrix2.size() > 0) {
            Map<String, Integer> colorMap = mCubeMatrix2.get(color);
            if (null != colorMap && colorMap.size() > 0) {
                int maxCnt = 0;
                for (String c : colorMap.keySet()) {
                    if (colorMap.get(c) > maxCnt) {
                        maxCnt = colorMap.get(c);
                    }
                }
                return maxCnt;
            }
        }
        return 0;
    }

    /**
     * ## Cube string notation
     * The names of the facelet positions of the cube (letters stand for Up, Left, Front, Right, Back, and Down):
     * ```
     * |************|
     * |*U1**U2**U3*|
     * |************|
     * |*U4**U5**U6*|
     * |************|
     * |*U7**U8**U9*|
     * |************|
     * |************|************|************|************|
     * |*L1**L2**L3*|*F1**F2**F3*|*R1**R2**R3*|*B1**B2**B3*|
     * |************|************|************|************|
     * |*L4**L5**L6*|*F4**F5**F6*|*R4**R5**R6*|*B4**B5**B6*|
     * |************|************|************|************|
     * |*L7**L8**L9*|*F7**F8**F9*|*R7**R8**R9*|*B7**B8**B9*|
     * |************|************|************|************|
     * |************|
     * |*D1**D2**D3*|
     * |************|
     * |*D4**D5**D6*|
     * |************|
     * |*D7**D8**D9*|
     * |************|
     * <p>
     * order:      URFDLB
     */
    private String tryCalculateRecoverStep() {
        Log.d(TAG, "tryCalculateRecoverStep: " + mCubeMatrix2.size());

        if (mCubeMatrix2.size() == 6 &&
                countAspectColor("WHITE") > START_PROCESS_CNT &&
                countAspectColor("YELLOW") > START_PROCESS_CNT &&
                countAspectColor("RED") > START_PROCESS_CNT &&
                countAspectColor("BLUE") > START_PROCESS_CNT &&
                countAspectColor("GREEN") > START_PROCESS_CNT &&
                countAspectColor("ORANGE") > START_PROCESS_CNT
        ) {
            changeState(BRAIN_STATE.CALCULATING);

//            String resultStep = tryCalculateRecoverStep();
//            Log.d(TAG, "mapColor2Coordinate: " + resultStep);
            String colorSerial = getMostPossibleColorSerial();


            String stepRes = mEngine.process(colorSerial);
            Log.d(TAG, "stepRes: " + stepRes);
            if (!TextUtils.isEmpty(stepRes) && !"NULL".equals(stepRes)) {
                return stepRes;
            } else {
                Log.w(TAG, "input not accuracy, need review");
            }
        } else {
            //go on find cube
            //todo debug ----------------------------------
            Log.d(TAG, "go on find cube: ----------------------------------");
            Log.d(TAG, "WHITE : " + countAspectColor("WHITE"));
            Log.d(TAG, "YELLOW: " + countAspectColor("YELLOW"));
            Log.d(TAG, "RED: " + countAspectColor("RED"));
            Log.d(TAG, "BLUE: " + countAspectColor("BLUE"));
            Log.d(TAG, "GREEN: " + countAspectColor("GREEN"));
            Log.d(TAG, "ORANGE: " + countAspectColor("ORANGE"));
            Log.d(TAG, "go on find cube: ----------------------------------");

        }

        return "";

//
//        if (mIsProcess) {
//            return "";
//        }
//
////        new Thread(new Runnable() {
////            @Override
////            public void run() {
//                mIsProcess = true;
////todo        myEyes.close();
//                //letters stand for Up, Left, Front, Right, Back, and Down
//                Log.d(TAG, "mapColor2Coordinate: build coordinate");
////                StringBuilder stepSb = new StringBuilder();
//                //"WHITE" <-> "Up"
//                //"RED" <-> "Right"
//                //"GREEN" <-> "Front"
//                //"YELLOW" <-> "Down"
//                //"ORANGE" <-> "Left"
//                //"BLUE" <-> "Back"
//                //version 1:too sample
////                stepSb.append(mCubeMatrix.remove("WHITE"));
////                stepSb.append(mCubeMatrix.remove("RED"));
////                stepSb.append(.remove("GREEN"));
////                stepSb.append(mCubeMatrix.remove("YELLOW"));
////                stepSb.append(mCubeMatrix.remove("ORANGE"));
////                stepSb.append(mCubeMatrix.remove("BLUE"));
////                String stepRes = mEngine.process(stepSb.toString());
//
//                //version 2:select aspect color according to it`s count
//                String colorSerial = getMostPossibleColorSerial();
//
//
//                String stepRes = mEngine.process(colorSerial);
//                Log.d(TAG, "stepRes: " + stepRes);
//                if (TextUtils.isEmpty(stepRes) || "NULL".equals(stepRes)) {
//                    Log.w(TAG, "input not accuracy, need review");
//                }
////                mCubeMatrix.clear();
//                mIsProcess = false;
////            }
////        }).start();
//
//        return "";
    }

    private String getMostPossibleColorSerial() {
        if (null == mCubeMatrix2 || mCubeMatrix2.size() < 6) {
            Log.w(TAG, "getMostPossibleColorSerial: mCubeMatrix2 is null or data too less");
            return null;
        }

        StringBuilder stepSb = new StringBuilder();

        Map<String, Integer> whiteMap = mCubeMatrix2.remove("WHITE");
        Map<String, Integer> redMap = mCubeMatrix2.remove("RED");
        Map<String, Integer> greenMap = mCubeMatrix2.remove("GREEN");
        Map<String, Integer> yellowMap = mCubeMatrix2.remove("YELLOW");
        Map<String, Integer> orangeMap = mCubeMatrix2.remove("ORANGE");
        Map<String, Integer> blueMap = mCubeMatrix2.remove("BLUE");

        //select color must be in order
        if (null != whiteMap && whiteMap.size() > 0) {
            String w = "";
            int wScore = 0;
            for (String wcSerial : whiteMap.keySet()) {
                if (whiteMap.get(wcSerial) > wScore) {
                    w = wcSerial;
                    wScore = whiteMap.get(wcSerial);
                }
            }
            if (wScore > 0 && !TextUtils.isEmpty(w)) {
                stepSb.append(w);
                Log.d(TAG, "getMostPossibleColorSerial: white[" + wScore + "]:" + w);
            }
        }

        if (null != redMap && redMap.size() > 0) {
            String r = "";
            int rScore = 0;
            for (String rcSerial : redMap.keySet()) {
                if (redMap.get(rcSerial) > rScore) {
                    r = rcSerial;
                    rScore = redMap.get(rcSerial);
                }
            }
            if (rScore > 0 && !TextUtils.isEmpty(r)) {
                stepSb.append(r);
                Log.d(TAG, "getMostPossibleColorSerial: red[" + rScore + "]:" + r);
            }
        }

        if (null != greenMap && greenMap.size() > 0) {
            String g = "";
            int gScore = 0;
            for (String wcSerial : greenMap.keySet()) {
                if (greenMap.get(wcSerial) > gScore) {
                    g = wcSerial;
                    gScore = greenMap.get(wcSerial);
                }
            }
            if (gScore > 0 && !TextUtils.isEmpty(g)) {
                stepSb.append(g);
                Log.d(TAG, "getMostPossibleColorSerial: green[" + gScore + "]:" + g);
            }
        }

        if (null != yellowMap && yellowMap.size() > 0) {
            String y = "";
            int yScore = 0;
            for (String wcSerial : yellowMap.keySet()) {
                if (yellowMap.get(wcSerial) > yScore) {
                    y = wcSerial;
                    yScore = yellowMap.get(wcSerial);
                }
            }
            if (yScore > 0 && !TextUtils.isEmpty(y)) {
                stepSb.append(y);
                Log.d(TAG, "getMostPossibleColorSerial: yellow[" + yScore + "]:" + y);
            }
        }

        if (null != orangeMap && orangeMap.size() > 0) {
            String o = "";
            int oScore = 0;
            for (String wcSerial : orangeMap.keySet()) {
                if (orangeMap.get(wcSerial) > oScore) {
                    o = wcSerial;
                    oScore = orangeMap.get(wcSerial);
                }
            }
            if (oScore > 0 && !TextUtils.isEmpty(o)) {
                stepSb.append(o);
                Log.d(TAG, "getMostPossibleColorSerial: orange[" + oScore + "]:" + o);
            }
        }

        if (null != blueMap && blueMap.size() > 0) {
            String b = "";
            int bScore = 0;
            for (String wcSerial : blueMap.keySet()) {
                if (blueMap.get(wcSerial) > bScore) {
                    b = wcSerial;
                    bScore = blueMap.get(wcSerial);
                }
            }
            if (bScore > 0 && !TextUtils.isEmpty(b)) {
                stepSb.append(b);
                Log.d(TAG, "getMostPossibleColorSerial: blue[" + bScore + "]:" + b);
            }
        }

        return stepSb.toString();
    }

}
